#ifndef C567B229_B3E1_449F_8B99_3B1BD9340276
#define C567B229_B3E1_449F_8B99_3B1BD9340276

#include <dlfcn.h>
#include <memory>
#include <string>

namespace mongols {

template <class T>
class module {
public:
    module()
        : handle(NULL)
        , create(NULL)
        , destroy(NULL)
        , path()
        , creator(false)
    {
    }

    module(const std::string& path)
        : handle(NULL)
        , create(NULL)
        , destroy(NULL)
        , path(path)
        , creator(false)
    {
        this->init();
    }

    module(const module<T>& other)
        : handle(other.handle)
        , create(other.create)
        , destroy(other.destroy)
        , path(other.path)
        , creator(false)
    {
    }

    module<T>& operator=(const module<T>& right)
    {
        if (this == &right)
            return *this;
        this->handle = right.handle;
        this->create = right.create;
        this->destroy = right.destroy;
        this->path = right.path;
        this->creator = false;
        return *this;
    }

    virtual ~module()
    {
        if (this->handle != NULL && this->creator) {
            dlclose(this->handle);
        }
    }

    void set_module(const std::string& path)
    {
        this->path = path;
        this->init();
    }

    const std::string& get_module() const
    {
        return this->path;
    }

    template <typename... Args>
    std::shared_ptr<T> make_obj(Args... args)
    {
        if (this->handle == NULL || this->create == NULL || this->destroy == NULL) {
            return std::shared_ptr<T>(NULL);
        }

        T* obj = this->create(args...);
        destroy_t* d = this->destroy;
        return std::shared_ptr<T>(obj,
            [&](T* p) {
                d(p);
            });
    }

private:
    typedef T* create_t();
    typedef void destroy_t(T*);
    void* handle;
    create_t* create;
    destroy_t* destroy;
    std::string path;
    bool creator;

    void init()
    {
        this->handle = dlopen(this->path.c_str(), RTLD_NOW);
        if (this->handle != NULL) {
            dlerror();
            this->create = (create_t*)dlsym(this->handle, "create");
            if (dlerror() != NULL)
                this->create = NULL;
            this->destroy = (destroy_t*)dlsym(this->handle, "destroy");
            if (dlerror() != NULL)
                this->destroy = NULL;

            if (this->create != NULL && this->destroy != NULL) {
                this->creator = true;
            }
        }
    }
};
}

#endif /* C567B229_B3E1_449F_8B99_3B1BD9340276 */
